// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/builtins/builtins-utils-gen.h"
#include "src/code-stub-assembler.h"
#include "src/objects-inl.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-opcodes.h"

namespace v8 {
namespace internal {

    class WasmBuiltinsAssembler : public CodeStubAssembler {
    public:
        explicit WasmBuiltinsAssembler(compiler::CodeAssemblerState* state)
            : CodeStubAssembler(state)
        {
        }

    protected:
        TNode<Object> UncheckedParameter(int index)
        {
            return UncheckedCast<Object>(Parameter(index));
        }

        TNode<Code> LoadBuiltinFromFrame(Builtins::Name id)
        {
            TNode<Object> instance = LoadInstanceFromFrame();
            TNode<IntPtrT> isolate_root = UncheckedCast<IntPtrT>(
                Load(MachineType::Pointer(), instance,
                    IntPtrConstant(WasmInstanceObject::kIsolateRootOffset - kHeapObjectTag)));
            TNode<Code> target = UncheckedCast<Code>(
                Load(MachineType::TaggedPointer(), isolate_root,
                    IntPtrConstant(IsolateData::builtin_slot_offset(id))));
            return target;
        }

        TNode<Object> LoadInstanceFromFrame()
        {
            return UncheckedCast<Object>(
                LoadFromParentFrame(WasmCompiledFrameConstants::kWasmInstanceOffset));
        }

        TNode<Object> LoadContextFromInstance(TNode<Object> instance)
        {
            return UncheckedCast<Object>(
                Load(MachineType::AnyTagged(), instance,
                    IntPtrConstant(WasmInstanceObject::kNativeContextOffset - kHeapObjectTag)));
        }

        TNode<Code> LoadCEntryFromInstance(TNode<Object> instance)
        {
            return UncheckedCast<Code>(
                Load(MachineType::AnyTagged(), instance,
                    IntPtrConstant(WasmInstanceObject::kCEntryStubOffset - kHeapObjectTag)));
        }
    };

    TF_BUILTIN(WasmAllocateHeapNumber, WasmBuiltinsAssembler)
    {
        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kAllocateHeapNumber);
        TailCallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant());
    }

    TF_BUILTIN(WasmCallJavaScript, WasmBuiltinsAssembler)
    {
        TNode<Object> context = UncheckedParameter(Descriptor::kContext);
        TNode<Object> function = UncheckedParameter(Descriptor::kFunction);
        TNode<Object> argc = UncheckedParameter(Descriptor::kActualArgumentsCount);
        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kCall_ReceiverIsAny);
        TailCallStub(CallTrampolineDescriptor {}, target, context, function, argc);
    }

    TF_BUILTIN(WasmRecordWrite, WasmBuiltinsAssembler)
    {
        TNode<Object> object = UncheckedParameter(Descriptor::kObject);
        TNode<Object> slot = UncheckedParameter(Descriptor::kSlot);
        TNode<Object> remembered = UncheckedParameter(Descriptor::kRememberedSet);
        TNode<Object> fp_mode = UncheckedParameter(Descriptor::kFPMode);
        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kRecordWrite);
        TailCallStub(RecordWriteDescriptor {}, target, NoContextConstant(), object,
            slot, remembered, fp_mode);
    }

    TF_BUILTIN(WasmToNumber, WasmBuiltinsAssembler)
    {
        TNode<Object> context = UncheckedParameter(Descriptor::kContext);
        TNode<Object> argument = UncheckedParameter(Descriptor::kArgument);
        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kToNumber);
        TailCallStub(TypeConversionDescriptor(), target, context, argument);
    }

    TF_BUILTIN(WasmStackGuard, WasmBuiltinsAssembler)
    {
        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);
        TNode<Object> context = LoadContextFromInstance(instance);
        TailCallRuntimeWithCEntry(Runtime::kWasmStackGuard, centry, context);
    }

    TF_BUILTIN(WasmStackOverflow, WasmBuiltinsAssembler)
    {
        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);
        TNode<Object> context = LoadContextFromInstance(instance);
        TailCallRuntimeWithCEntry(Runtime::kThrowWasmStackOverflow, centry, context);
    }

    TF_BUILTIN(WasmThrow, WasmBuiltinsAssembler)
    {
        TNode<Object> exception = UncheckedParameter(Descriptor::kException);
        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);
        TNode<Object> context = LoadContextFromInstance(instance);
        TailCallRuntimeWithCEntry(Runtime::kThrow, centry, context, exception);
    }

    TF_BUILTIN(WasmAtomicNotify, WasmBuiltinsAssembler)
    {
        TNode<Uint32T> address = UncheckedCast<Uint32T>(Parameter(Descriptor::kAddress));
        TNode<Uint32T> count = UncheckedCast<Uint32T>(Parameter(Descriptor::kCount));

        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);

        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kAllocateHeapNumber);

        // TODO(aseemgarg): Use SMIs if possible for address and count
        TNode<HeapNumber> address_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(address_heap, ChangeUint32ToFloat64(address));

        TNode<HeapNumber> count_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(count_heap, ChangeUint32ToFloat64(count));

        TNode<Smi> result_smi = UncheckedCast<Smi>(CallRuntimeWithCEntry(
            Runtime::kWasmAtomicNotify, centry, NoContextConstant(), instance,
            address_heap, count_heap));
        ReturnRaw(SmiToInt32(result_smi));
    }

    TF_BUILTIN(WasmI32AtomicWait, WasmBuiltinsAssembler)
    {
        TNode<Uint32T> address = UncheckedCast<Uint32T>(Parameter(Descriptor::kAddress));
        TNode<Int32T> expected_value = UncheckedCast<Int32T>(Parameter(Descriptor::kExpectedValue));
        TNode<Float64T> timeout = UncheckedCast<Float64T>(Parameter(Descriptor::kTimeout));

        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);

        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kAllocateHeapNumber);

        // TODO(aseemgarg): Use SMIs if possible for address and expected_value
        TNode<HeapNumber> address_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(address_heap, ChangeUint32ToFloat64(address));

        TNode<HeapNumber> expected_value_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(expected_value_heap,
            ChangeInt32ToFloat64(expected_value));

        TNode<HeapNumber> timeout_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(timeout_heap, timeout);

        TNode<Smi> result_smi = UncheckedCast<Smi>(CallRuntimeWithCEntry(
            Runtime::kWasmI32AtomicWait, centry, NoContextConstant(), instance,
            address_heap, expected_value_heap, timeout_heap));
        ReturnRaw(SmiToInt32(result_smi));
    }

    TF_BUILTIN(WasmI64AtomicWait, WasmBuiltinsAssembler)
    {
        TNode<Uint32T> address = UncheckedCast<Uint32T>(Parameter(Descriptor::kAddress));
        TNode<Uint32T> expected_value_high = UncheckedCast<Uint32T>(Parameter(Descriptor::kExpectedValueHigh));
        TNode<Uint32T> expected_value_low = UncheckedCast<Uint32T>(Parameter(Descriptor::kExpectedValueLow));
        TNode<Float64T> timeout = UncheckedCast<Float64T>(Parameter(Descriptor::kTimeout));

        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);

        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kAllocateHeapNumber);

        // TODO(aseemgarg): Use SMIs if possible for address and expected_value
        TNode<HeapNumber> address_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(address_heap, ChangeUint32ToFloat64(address));

        TNode<HeapNumber> expected_value_high_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(expected_value_high_heap,
            ChangeUint32ToFloat64(expected_value_high));

        TNode<HeapNumber> expected_value_low_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(expected_value_low_heap,
            ChangeUint32ToFloat64(expected_value_low));

        TNode<HeapNumber> timeout_heap = UncheckedCast<HeapNumber>(
            CallStub(AllocateHeapNumberDescriptor(), target, NoContextConstant()));
        StoreHeapNumberValue(timeout_heap, timeout);

        TNode<Smi> result_smi = UncheckedCast<Smi>(CallRuntimeWithCEntry(
            Runtime::kWasmI64AtomicWait, centry, NoContextConstant(), instance,
            address_heap, expected_value_high_heap, expected_value_low_heap,
            timeout_heap));
        ReturnRaw(SmiToInt32(result_smi));
    }

    TF_BUILTIN(WasmMemoryGrow, WasmBuiltinsAssembler)
    {
        TNode<Int32T> num_pages = UncheckedCast<Int32T>(Parameter(Descriptor::kNumPages));
        Label num_pages_out_of_range(this, Label::kDeferred);

        TNode<BoolT> num_pages_fits_in_smi = IsValidPositiveSmi(ChangeInt32ToIntPtr(num_pages));
        GotoIfNot(num_pages_fits_in_smi, &num_pages_out_of_range);

        TNode<Smi> num_pages_smi = SmiFromInt32(num_pages);
        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);
        TNode<Object> context = LoadContextFromInstance(instance);
        TNode<Smi> ret_smi = UncheckedCast<Smi>(CallRuntimeWithCEntry(
            Runtime::kWasmMemoryGrow, centry, context, instance, num_pages_smi));
        TNode<Int32T> ret = SmiToInt32(ret_smi);
        ReturnRaw(ret);

        BIND(&num_pages_out_of_range);
        ReturnRaw(Int32Constant(-1));
    }

    TF_BUILTIN(WasmTableGet, WasmBuiltinsAssembler)
    {
        TNode<Int32T> entry_index = UncheckedCast<Int32T>(Parameter(Descriptor::kEntryIndex));
        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);
        TNode<Object> context = LoadContextFromInstance(instance);
        Label entry_index_out_of_range(this, Label::kDeferred);

        TNode<BoolT> entry_index_fits_in_smi = IsValidPositiveSmi(ChangeInt32ToIntPtr(entry_index));
        GotoIfNot(entry_index_fits_in_smi, &entry_index_out_of_range);

        TNode<Smi> entry_index_smi = SmiFromInt32(entry_index);
        TNode<Smi> table_index_smi = UncheckedCast<Smi>(Parameter(Descriptor::kTableIndex));

        TailCallRuntimeWithCEntry(Runtime::kWasmFunctionTableGet, centry, context,
            instance, table_index_smi, entry_index_smi);

        BIND(&entry_index_out_of_range);
        MessageTemplate message_id = wasm::WasmOpcodes::TrapReasonToMessageId(wasm::kTrapTableOutOfBounds);
        TailCallRuntimeWithCEntry(Runtime::kThrowWasmError, centry, context,
            SmiConstant(static_cast<int>(message_id)));
    }

    TF_BUILTIN(WasmTableSet, WasmBuiltinsAssembler)
    {
        TNode<Int32T> entry_index = UncheckedCast<Int32T>(Parameter(Descriptor::kEntryIndex));
        TNode<Object> instance = LoadInstanceFromFrame();
        TNode<Code> centry = LoadCEntryFromInstance(instance);
        TNode<Object> context = LoadContextFromInstance(instance);
        Label entry_index_out_of_range(this, Label::kDeferred);

        TNode<BoolT> entry_index_fits_in_smi = IsValidPositiveSmi(ChangeInt32ToIntPtr(entry_index));
        GotoIfNot(entry_index_fits_in_smi, &entry_index_out_of_range);

        TNode<Smi> entry_index_smi = SmiFromInt32(entry_index);
        TNode<Smi> table_index_smi = UncheckedCast<Smi>(Parameter(Descriptor::kTableIndex));
        TNode<Object> value = UncheckedCast<Object>(Parameter(Descriptor::kValue));
        TailCallRuntimeWithCEntry(Runtime::kWasmFunctionTableSet, centry, context,
            instance, table_index_smi, entry_index_smi, value);

        BIND(&entry_index_out_of_range);
        MessageTemplate message_id = wasm::WasmOpcodes::TrapReasonToMessageId(wasm::kTrapTableOutOfBounds);
        TailCallRuntimeWithCEntry(Runtime::kThrowWasmError, centry, context,
            SmiConstant(static_cast<int>(message_id)));
    }

    TF_BUILTIN(WasmI64ToBigInt, WasmBuiltinsAssembler)
    {
        if (!Is64()) {
            Unreachable();
            return;
        }

        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kI64ToBigInt);
        TNode<IntPtrT> argument = UncheckedCast<IntPtrT>(Parameter(Descriptor::kArgument));

        TailCallStub(I64ToBigIntDescriptor(), target, NoContextConstant(), argument);
    }

    TF_BUILTIN(WasmBigIntToI64, WasmBuiltinsAssembler)
    {
        if (!Is64()) {
            Unreachable();
            return;
        }

        TNode<Object> context = UncheckedCast<Object>(Parameter(Descriptor::kContext));
        TNode<Code> target = LoadBuiltinFromFrame(Builtins::kBigIntToI64);
        TNode<IntPtrT> argument = UncheckedCast<IntPtrT>(Parameter(Descriptor::kArgument));

        TailCallStub(BigIntToI64Descriptor(), target, context, argument);
    }

#define DECLARE_ENUM(name)                                                                    \
    TF_BUILTIN(ThrowWasm##name, WasmBuiltinsAssembler)                                        \
    {                                                                                         \
        TNode<Object> instance = LoadInstanceFromFrame();                                     \
        TNode<Code> centry = LoadCEntryFromInstance(instance);                                \
        TNode<Object> context = LoadContextFromInstance(instance);                            \
        MessageTemplate message_id = wasm::WasmOpcodes::TrapReasonToMessageId(wasm::k##name); \
        TailCallRuntimeWithCEntry(Runtime::kThrowWasmError, centry, context,                  \
            SmiConstant(static_cast<int>(message_id)));                                       \
    }
    FOREACH_WASM_TRAPREASON(DECLARE_ENUM)
#undef DECLARE_ENUM

} // namespace internal
} // namespace v8
